
package com.wgo.precise.client.ui.model;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ICellEditorValidator;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

import com.wgo.precise.client.ui.controller.ModelStatus;
import com.wgo.precise.client.ui.controller.RequirementPlugin;
import com.wgo.precise.client.ui.controller.exceptions.RematoClientException;
import com.wgo.precise.client.ui.view.util.LinkListPropertySource;
import com.wgo.precise.client.ui.view.util.ModelByteArrayTransfer;
import com.wgo.precise.client.ui.view.util.celleditors.LinkedTextCellEditor;
import com.wgo.precise.client.ui.view.util.celleditors.RequirementsCellEditor;
import com.wgo.precise.client.ui.view.util.celleditors.StakeholderCellEditor;
import com.wgo.precise.client.ui.view.util.celleditors.TextDialogCellEditor;
import com.wgo.precise.client.ui.view.util.validators.UrlInputValidator;


/**
 * @author Petter L. H. Eide
 * @version $Id: PropertyType.java,v 1.7 2006-03-07 19:45:51 petterei Exp $
 */
public enum PropertyType {

	DATE(Date.class, "Date value"){
		private DateFormat dateFormatter;
		@Override
		public CellEditor getCellEditor(Composite parent) {
			CellEditor dateCellEditor = new TextCellEditor(parent);
			dateCellEditor.setValidator(
					new ICellEditorValidator(){
						public String isValid(Object value) {
							try {
								getDateFormatter().parse((String)value);
								return null;
							} catch (ParseException e1) {
								return "Invalid date. Use format: DD/MM/YYYY";
							}
						}						
					});
			return dateCellEditor;
		}
		@Override
		public Object createObjectValue(String text) {
			Date date = new Date(0);
			if (null != text) {
				try {
					date = getDateFormatter().parse(text);
				} catch (ParseException ignoreException) {}
			}
			return date;
		}
		@Override
		public String createStringValue(Object value) {
			if (null == value) {
				value = new Date();
			}
			String result = value.toString();
			if (value instanceof Date) {
				result = getDateFormatter().format(value);
			}
			return result;
		}
		public DateFormat getDateFormatter(){
			if (null == dateFormatter) {
				dateFormatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
				dateFormatter.setLenient(false);
			}
			return dateFormatter;
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			Object cellValue = null ;
			if (null == value || value.equals(new Date(0))){
				cellValue = "";
			} else {
				cellValue = createStringValue(value);
			}
			return cellValue;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			Object value = null;
			if (null != cellValue){
				value = createObjectValue((String)cellValue);
			}
			return value;
		}
	},
	
	STRING(String.class, "String value"){
		@Override
		public CellEditor getCellEditor(Composite parent) {
			TextCellEditor textEditor = new TextCellEditor(parent);
			// DatabaseLimitation:
			((Text) textEditor.getControl()).setTextLimit(256);
			return textEditor;
		}
		@Override
		public Object createObjectValue(String text) {
			return text;
		}
		@Override
		public String createStringValue(Object value) {
			return (String)value;
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			if (null == value) {
				value = "";
			}
			return value;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return cellValue;
		}
		
	}
	,
	INTEGER(Long.class, "Integer value"){
		@Override
		public CellEditor getCellEditor(Composite parent) {
			TextCellEditor textCellEditor = new TextCellEditor(parent);
			((Text)textCellEditor.getControl()).addVerifyListener(
					new VerifyListener() {
						public void verifyText(VerifyEvent e) {
							// Here, we could use a RegExp such as the following 
							// if using JRE1.4 such as  e.doit = e.text.matches("[\\-0-9]*");
//							e.doit = null != Integer.getInteger(e.text) ;
							e.doit = e.text.matches("[0-9]*");
						}
					});
			return textCellEditor;
		}
		@Override
		public Object createObjectValue(String text) {
			Object result = createNewValue();
			if (null != text && text != ""){
				result = new Long(text);
			} else {
				result = null;
			}
			return result;
		}
		@Override
		public String createStringValue(Object value) {
			String stringValue = "";
			if (value != null) {
				Long longValue = (Long) value; // for validation
				stringValue = String.valueOf(longValue).trim(); 
			}
			return stringValue;
		}
		@Override
		protected Object createNewValue() {
			return new Long(0);
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			return createStringValue(value);
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return createObjectValue((String)cellValue);
		}
		
	}
	,
	DOUBLE(Double.class, "Double value"){
		@Override
		public CellEditor getCellEditor(Composite parent) {
			TextCellEditor textCellEditor = new TextCellEditor(parent);
			((Text)textCellEditor.getControl()).addVerifyListener(
					new VerifyListener() {
						public void verifyText(VerifyEvent e) {
							// Here, we could use a RegExp such as the following 
							// if using JRE1.4 such as  e.doit = e.text.matches("[\\-0-9]*");
//							e.doit = null != Integer.getInteger(e.text) ;
							e.doit = e.text.matches("[0-9]*("+Character.DECIMAL_DIGIT_NUMBER+")?[0-9]*");
							
						}
					});
			return textCellEditor;
		}
		@Override
		public Object createObjectValue(String text) {
			Object result = createNewValue();
			if (null != text && text != ""){
				result = new Double(text);
			} else {
				result = null;
			}
			return result;
		}
		@Override
		public String createStringValue(Object value) {
			String stringValue = "";
			if (value != null) {
				Double doubleValue = (Double) value; // for validation
				stringValue = String.valueOf(doubleValue).trim(); 
			}
			return stringValue;
		}
		@Override
		protected Object createNewValue() {
			return new Double(0);
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			return createStringValue(value);
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return createObjectValue((String)cellValue);
		}
	}
	,
	TEXT (String.class, "String value") {
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			if (null == value) {
				value = "";
			}
			return value;
		}
		@Override
		public Object createObjectValue(String value) {
			return value;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return cellValue;
		}
		@Override
		public String createStringValue(Object value) {
			return (String)value;
		}
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return new TextDialogCellEditor(parent);
		}
	}
	,
	ENUMERATION(Integer.class, "Enumeration value") {
		@Override
		public Object createObjectValue(String value) {
			return new Integer(value);
		}
		@Override
		public String createStringValue(Object value) {
			return String.valueOf(value).trim();
		}
		/**
		 * @deprecated Enumeration, thus use getCellComboBoxEditor(Composite, String[])
		 */
		@Deprecated
		@SuppressWarnings("unused") // Deprecated, thus empty and parameters not used
		@Override
		public CellEditor getCellEditor(Composite parent) {
			throw new RematoClientException("Should not be called for enumeration-type.");
		}
		@Override
		public CellEditor getCellComboBoxEditor(Composite parent, String[] alternatives) {
			return super.getCellComboBoxEditor(parent, alternatives);
		}
		@Override
		public Object createCellEditorValueFromObject(Object value) {
			if (!(value instanceof Integer)) {
				ClassCastException e = new ClassCastException("Not valid value-type for Enumeration-type.");
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid type.", e));
				throw e;
			}
			return value;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			if (!(cellValue instanceof Integer)) {
				ClassCastException e = new ClassCastException("Not valid value-type for Enumeration-type.");
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid type.", e));
				throw e;
			}
			return cellValue;
		}
		@SuppressWarnings("unchecked")
		@Override
		public PropertyDescriptor getDescriptor(AbstractPropertySpesification id, String label) {
			final List<String> enumerationElements = ((EnumPropertySpecification)id).getEnumerationElements();
			return new ComboBoxPropertyDescriptor(id, label, enumerationElements.toArray(new String[enumerationElements.size()])) {
				/**
				 * @see org.eclipse.ui.views.properties.PropertyDescriptor#createPropertyEditor(org.eclipse.swt.widgets.Composite)
				 */
				@Override
				public CellEditor createPropertyEditor(Composite parent) {
					ComboBoxCellEditor comboCellEditor = new ComboBoxCellEditor(
							parent,
							enumerationElements
							.toArray(new String[enumerationElements.size()]));
					comboCellEditor.setStyle(SWT.READ_ONLY);
					return comboCellEditor;
				}
			};
		}
		
	}
	,
	REQUIREMENT_COLLECTION(List.class, "Requirements"){
		@Override
		public Object createObjectValue(String value) {
			// TODO if possible to use as user-defined types..
			return null;
		}
		@Override
		public String createStringValue(Object value) {
			// TODO if possible to use as user-defined types..
			return null;
		}
		@Override
		public CellEditor getCellEditor(Composite parent) {
			CellEditor cell = new RequirementsCellEditor(parent); 
			DropTarget target = new DropTarget(cell.getControl(), DND.DROP_LINK);
			target.setTransfer(ModelByteArrayTransfer.getDefaultTrancfer());
			target.addDropListener(new DropTargetListenerImpl(cell));
			return cell;
		}
		@Override
		protected Object createNewValue() {
			return null;
		}
		@SuppressWarnings("unchecked")
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			LinkListPropertySource list = null;
			if (null != value && value instanceof List ) {
				List valueList = (List)value;
				if (0 < valueList.size()) {
					list = new LinkListPropertySource(valueList);
				}
			} else if (null != value ){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + REQUIREMENT_COLLECTION.name() + ": DomainModelObject returned invalid value."));
			}
			return list;
		}
		@SuppressWarnings("unchecked")
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			Collection list = new ArrayList();
			if (null != cellValue && cellValue instanceof LinkListPropertySource){
				list.addAll(((LinkListPropertySource)cellValue).getItems());
			} else if (null != cellValue ){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + REQUIREMENT_COLLECTION.name() + ": CellEditor returned invalid value."));
			}
			return list;
		}
	}
	,
	STAKEHOLDER(StakeholderWrapper.class, "StakeholderWrapper"){
		@Override
		public StakeholderWrapper createObjectValue(String value) {
			StakeholderWrapper objectValue = null;
			if (value != null) {
				long boxedLongValue = Long.parseLong(value);
				if (0 != boxedLongValue) {
					Long longValue = new Long(boxedLongValue);
					objectValue = (StakeholderWrapper)RequirementPlugin.getInstance().getSession().getModelItem(longValue);
				} else {
					RequirementPlugin.log(new ModelStatus(IStatus.ERROR, getClass().getSimpleName() + " Invalid identifier (0) for stakeholder."));
				}
			}
			return objectValue;
		}
		@Override
		public String createStringValue(Object value) {
			String stringValue = null;
			if (null != value && value instanceof StakeholderWrapper) {
				//TODO: should have ID's which is valid when concepts is not persisted
				stringValue = String.valueOf(((StakeholderWrapper)value).getIdentifier());
			} else if (null != value) {
				RematoClientException e = new RematoClientException("Not valid value-type for " + StakeholderWrapper.class.getName());
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid type.", e));
				throw e;
			}
			return stringValue;
		}
		
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return new StakeholderCellEditor(parent, false);
		}
		@Override
		protected Object createNewValue() {
			return null;
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			LinkListPropertySource cellValue = null;
			if (value != null && value instanceof StakeholderWrapper) {
				cellValue = new LinkListPropertySource((StakeholderWrapper)value);
			} else if (null != value){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + STAKEHOLDER.name() + ": DomainModelObject returned invalid value."));
			}
			return cellValue;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			StakeholderWrapper wrapper = null;
			if (null != cellValue && cellValue instanceof LinkListPropertySource) {
				List list = ((LinkListPropertySource)cellValue).getItems();
				if (0 < list.size()) {
					wrapper =(StakeholderWrapper)list.get(0);
				}
				if (1 < list.size()) {
					RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + STAKEHOLDER.name() + ": CellEditor returned invalid value. More than one elements, first element used."));
				}
			} else if (null != cellValue) {
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + STAKEHOLDER.name() + ": CellEditor returned invalid value. Unknown type."));
			}
			return wrapper;
		}
	}
	,
	STAKEHOLDER_COLLECTION(Collection.class, "List of stakeholders"){
		@Override
		public Object createObjectValue(String value) {
			//TODO
			return null;
		}
		@Override
		public String createStringValue(Object value) {
			//TODO
			return null;
		}
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return new StakeholderCellEditor(parent, true);
		}
		@SuppressWarnings("unchecked")
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			LinkListPropertySource cellValue = null;
			if (null != value && value instanceof Collection) {
				List valueList = (List)value;
				if (0 < valueList.size()) {
					cellValue = new LinkListPropertySource(valueList);
				}
			} else if (null != value ){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + STAKEHOLDER_COLLECTION.name() + ": DomainModelObject returned invalid value."));
			}
			return cellValue;
		}
		@SuppressWarnings("unchecked")
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			Collection list = null;
			if (null != cellValue && cellValue instanceof LinkListPropertySource) {
				list = new ArrayList(((LinkListPropertySource)cellValue).getItems());
			} else if (null != cellValue ){
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass() + " " + STAKEHOLDER_COLLECTION.name() + ": CellEditor returned invalid value."));
			}
			return list;
		}
		@Override
		protected Object createNewValue() {
			return null;
		}
	}
	,
	URL(URL.class, "Link as URL"){
		@Override
		public URL createObjectValue(String value) {
			URL objectValue = null;
			if (value != null) {
				try {
					objectValue = new URL(value);
				} catch (MalformedURLException e) {
					RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass().getSimpleName() + " Malformed URL, value not saved.", e)); // TODO: logAndDisplay?
				}
			}
			return objectValue;
		}
		@Override
		public String createStringValue(Object value) {
			String stringValue = null;
			if (null != value && value instanceof URL) {
				stringValue = ((URL)value).toExternalForm();
			} else if (null != value) {
				RematoClientException e = new RematoClientException("Not valid value-type for enum " + name() + ". Expected: "+ getClazz().getSimpleName() + ", was " + value.getClass().getSimpleName() + ". Value: " + value);
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid type.", e));
				throw e;
			}
			return stringValue;
		}
		
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return new LinkedTextCellEditor(parent);
		}
		@Override
		protected Object createNewValue() {
			return null;
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			String errorMessage = new UrlInputValidator().isValid(value);
			if (null != errorMessage) {
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid URL: " + errorMessage + " (" + value + ")"));
			}
			return value;
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			String errorMessage = new UrlInputValidator().isValid(cellValue);
			if (null != errorMessage) {
				RequirementPlugin.log(new ModelStatus(IStatus.ERROR, "Invalid URL: " + errorMessage + " (" + cellValue + ")"));
			}
			return cellValue;
		}
	}
	,
	PROPERTY_TYPE(PropertyType.class, "Property value type"){
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return getCellComboBoxEditor(parent, getFullStringDescriptions());
		}
		@Override
		public Object createObjectValue(String text) {
			return valueOf(text);
		}
		@Override
		public String createStringValue(Object value) {
			return ((PropertyType)value).name();
		}
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			return createStringValue(value); // integer?
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return createObjectValue((String)cellValue); //integer?
		}
	}
	,
	BOOLEAN(Boolean.class, "Boolean value") {
		@Override
		protected Object createCellEditorValueFromObject(Object value) {
			return value;
		}
		@Override
		public Object createObjectValue(String value) {
			return Boolean.valueOf(value);
		}
		@Override
		protected Object createObjectValueFromCellEditor(Object cellValue) {
			return cellValue;
		}
		@Override
		public String createStringValue(Object value) {
			return Boolean.toString((Boolean)value);
		}
		@Override
		public CellEditor getCellEditor(Composite parent) {
			return new CheckboxCellEditor(parent);
		}
		@Override
		protected Object createNewValue() {
			return new Boolean(false);
		}
	}
	;
	private Class clazz;
	private String description;
	private PropertyType(Class clazz, String description) {
		this.clazz = clazz;
		this.description = description;
	}
	public Class getClazz() {
		return clazz;
	}
	public String getDescription() {
		return description;
	}
	public String getFullDescription() {
		return description + " (" + name() + ")";
	}
	
	public static String[] getStringValues() {
		int enums = values().length - 1;
		String[] stringValues = new String[enums];
		for(int i = 0; i < enums; i++){
			if (values()[i] != PROPERTY_TYPE) {
				stringValues[i] = values()[i].name();
			} else {
				i--;
			}
		}
		return stringValues;
	}
	public static String[] getFullStringDescriptions() {
		int enums = values().length - 1;
		String[] stringValues = new String[enums];
		for (int i = 0; i < enums; i++) {
			if (values()[i] != PROPERTY_TYPE) {
				stringValues[i] = values()[i].getFullDescription();
			} else {
				i--;
			}
		}
		return stringValues;
	}
	
	public abstract CellEditor getCellEditor(Composite parent) ;
	
	public abstract String createStringValue(Object value);
	public abstract Object createObjectValue(String value);
	
	protected abstract Object createCellEditorValueFromObject(Object value);
	protected abstract Object createObjectValueFromCellEditor(Object cellValue);
	
	protected Object createNewValue() {
		try {
			return clazz.newInstance();
		} catch (Exception e) {
			throw new RematoClientException("Error when trying to instanciate new value: " + clazz.getSimpleName() , e);
		}
	}
	
	protected CellEditor getCellComboBoxEditor(Composite parent, String[] alternatives) {
		ComboBoxCellEditor comboCellEditor = new ComboBoxCellEditor(parent, alternatives);
		comboCellEditor.setStyle(SWT.READ_ONLY);
		return comboCellEditor;
	}
	
	protected PropertyDescriptor getDescriptor(AbstractPropertySpesification id, String label) {
		PropertyDescriptor descriptor = new TextPropertyDescriptor(id, label) {
			/**
			 * @see org.eclipse.ui.views.properties.PropertyDescriptor#createPropertyEditor(org.eclipse.swt.widgets.Composite)
			 */
			@Override
			public CellEditor createPropertyEditor(Composite parent) {
				return ((AbstractPropertySpesification)getId()).getCellEditor(parent);
			}
		};
		return descriptor;
	}
	
	public static PropertyType[] getPropertyTypes() {
		return new PropertyType[]{STRING, INTEGER, DATE, TEXT, URL, BOOLEAN, DOUBLE };
	}
}





class DropTargetListenerImpl extends DropTargetAdapter implements DropTargetListener {
	
	private CellEditor cell = null;
	
	public DropTargetListenerImpl(CellEditor cell) {
		this.cell = cell;
	}
	@Override
	public void dragEnter(DropTargetEvent event) {
		if (cell.getControl().isEnabled()) {
			event.detail = DND.DROP_LINK;
		} else {
			event.detail = DND.DROP_NONE;
		}
//		// will accept text but prefer to have files dropped
//		for (int i = 0; i < event.dataTypes.length; i++) {
//		if (fileTransfer.isSupportedType(event.dataTypes[i])){
//		event.currentDataType = event.dataTypes[i];
//		// files should only be copied
//		if (event.detail != DND.DROP_COPY) {
//		event.detail = DND.DROP_NONE;
//		}
//		break;
//		}
//		}
	}
	@Override
	public void dragOver(DropTargetEvent event) {
//		event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
		if (ModelByteArrayTransfer.getInstance().isSupportedType(event.currentDataType)) {
//			// NOTE: on unsupported platforms this will return null
//			Object o = textTransfer.nativeToJava(event.currentDataType);
//			String t = (String)o;
//			if (t != null) System.out.println(t);
		}
	}
	@Override
	public void dragOperationChanged(DropTargetEvent event) {
		dragEnter(event);
//		if (event.detail == DND.DROP_DEFAULT) {
//		if ((event.operations & DND.DROP_COPY) != 0) {
//		event.detail = DND.DROP_COPY;
//		} else {
//		event.detail = DND.DROP_NONE;
//		}
//		}
//		// allow text to be moved but files should only be copied
//		if (fileTransfer.isSupportedType(event.currentDataType)){
//		if (event.detail != DND.DROP_COPY) {
//		event.detail = DND.DROP_NONE;
//		}
//		}
	}
	@Override
	public void dragLeave(DropTargetEvent event) {
		event.toString();
	}
	@Override
	public void dropAccept(DropTargetEvent event) {
		event.toString();
	}
	@Override
	public void drop(DropTargetEvent event) {
		if (ModelByteArrayTransfer.getInstance().isSupportedType(event.currentDataType)) {
			
		}
//		String text = (String)event.data;
//		TableItem item = new TableItem(dropTable, SWT.NONE);
//		item.setText(text);
//		}
//		if (fileTransfer.isSupportedType(event.currentDataType)){
//		String[] files = (String[])event.data;
//		for (int i = 0; i < files.length; i++) {
//		TableItem item = new TableItem(dropTable, SWT.NONE);
//		item.setText(files[i]);
//		}
//		}
	}
	
}

